



additional features
    - cli autocompleate


# parser for .backup config format
such config file
~~~
# comment

# interpolations:
#    use curly brackets with name inside kinda like python f string formatting
#
#    available:
#        left  curly bracket    use {left_curly}          or {l}
#        right curly bracket    use {right_curly}         or {r}
#        space                  use {space}               or {s}
#        colon                  use {colon}               or {c}
#        hash                   use {hash}                or {h}
#        hyphen/minus           use {hyphen}/{minus}      or {m}
#        triple hyphen/minus    use {3hyphen}/{3minus}    or {mmm}  # m&m&m's

key_1:val
key_2: val         # inline comment
key_3:  val

# key_1: val        # will throw ValueError duplicated key "key_1"

list_1:
    -              # empty value

    -val-1 -       # notice hyphen-s/minus-es in value only '- '|'-' present
    - -- val-2     # at the lines BEGINNING is significant and will be
    - val-3---     # be stripped alongside indentation
                   # all spaces after last character are stripped
                   # empty lines and inline comments are ignored

    -  val-4       #  notice second space marked as _ in '- _val-4'

list_2:            # first value indentation sets indentation for whole scope
 - val-1           # indentation must be spaces, tabs are not permitted

 - use {l}name{r} for interpolations
 - white{c} {h}FFFFFF
 - black{c} {h}000000


raw_list:
    - exact name can be specified as raw before parsing on parser side
    - in such case everything except first '{indentation}- '|'{indentation}-'
    - will be taken as value until end of line is reached
    -      %03{*&}'"-[@#]($%)F:o+{space}  # comments and trailing spaces are ignored

raw_value: %03{*&}'"-[@#]($%)F:o+{space}  # comments and trailing spaces are ignored

# dict:            # dicts are unavailable and will be treated as empty values
#                  # empty values will throw ValueError
#  val_11: foo     # this will throw IndentationError
~~~
will get you
~~~
{
    'key_1': 'val',
    'key_2': 'val',
    'key_3': ' val',
    'list_1': [
        '',
        'val-1 -',
        '-- val-2',
        'val-3---',
        ' val-4',
    ],
    'list_2': [
        'val-1',
        'use {name} for interpolations',
        'white: #FFFFFF',
        'black: #000000',
    ],
    'raw_list': [
        'exact name can be specified as raw before parsing on parser side',
        'in such case everything except first "{indentation}- "',
        'will be taken as value untiil end of line is reached',
        '     %03{*&}'"-[@#]($%)F:o+{space}',
    ],
    'raw_key': '     %03{*&}'"-[@#]($%)F:o+{space}',
}
~~~

#

in_list = False
in_val = False
last = ''

take one char at a time
  if there are no char-s left end
  if char==space and in_list==False throw error
  if char==# read and ignore all char-s until \n
  if char==- and in_val==False and in_list==False throw error
  if char==: and in_val==False and in_list==False throw error
  read char-s and add it to current until :
  set in_val==True
  if char==space ignor






in_comment = False
in_list = False
in_value = False
indent = 0
current = ''
current_key = ''
current_val = []  # list[str]

take one ch at a time
  if ch==#: set in_comment=True and ignore current ch
  if in_comment==True: ignore current ch
  if ch==\n:
    if in_value:
      current_val.append(current)
      current=''
      in_val=False
    if in_list=True: do not reset current_key and current_value
    .
    reset all relevant variables (in_comment, ...)
  if ch==space:
    if not in_list: throw error
    if in_list: indent+=1 and ignore current ch
  if ch==':':
    current_key=current
    current=''
    in_value=True

  current+=ch




################################################################################
#
# first step only keys colons values and comments
#
# keys cannot be indented,
# keys and values are separated by ":" or ": "
# values continue to the end of line or #
# values trailing spaces are trimmed
# comments (start with # and continue till \n) are ignored
# empty lines and lines with only spaces are ignored
# no empty values
# keys cannot repeat
#

config = {}
current = ''
in_comment = False
in_val = False
current_key = ''

for ch in text ->
  if ch==# -> in_comment=True && continue
  if in_comment -> continue
  if ch==space:
    if not in_val -> throw error
  if ch==':' ->
    if current_key in config -> throw error
    in_val=True
    current_key=current
    current=''

  current+=ch

  if ch==\n:
    if in_val ->
      current->remove first space if applicable  # "key -> val" situation
      config[current_key]=current
    current=''
    current_key=''
    in_val=False
    in_comment=False

################################################################################
#
# second step add parsing of interpolations
#
# interpolation allowed only in values
# interpolation has to be contained in single line
# '{' is are not allowed in interpolation -> no nesting
#

config = {}
current = ''
in_val = False
in_comment = False
in_interpolation = False
interpolation = ''
current_key = ''

for ch in text ->
  if ch==# ->
      in_comment=True
      continue
  if in_comment -> continue

  if ch==space ->
      if not in_val -> throw error

  if ch==':' ->
      if current_key in config -> throw error
      in_val=True
      current_key=current
      current=''
      continue

  if ch=={ ->
      if in_val             -> set in_interpolation=True
      elif in_interpolation -> throw err    # nested interpolations
      else                  -> throw error  # interpolation outside value
  if ch==} ->
      if in_interpolation ->
          current+=process(interpolation)
          set in_interpolation=False
          continue
      else -> throw error
  if in_interpolation ->
      interpolation+=ch
      continue

  if ch==\n ->
      if in_interpolation -> throw error    
      if in_val ->
          config[current_key]=del_1st_space_if_relevant(current) # "key: val" situation
      current=''
      current_key=''
      interpolation=''
      in_val=False
      in_comment=False
      in_interpolation=False
      continue

  current+=ch

####### add tests
val ends in interpolation
interpolation not closed

################################################################################
#
# third step add parsing of list
#
# no empty lists
# no empty list values
# every list val must be indented
# every list val is contained in single line
# indentation of val on single list is taken form it's first value
# indentation of val of single list must be the same
# indentation of val on different lists can be different
# only first list item '-' character is significant
# if there is a space after list item character '- ' it will be stripped form val
#



config = {}
in_val = False
in_list = False
in_comment = False
in_interpolation = False
indent = 0
indent_expected = 0
current = ''
current_ke A'y = ''
interpolation = ''
list_items = []0


for ch in text ->
  if ch==# ->
      in_comment=True
      continue
  if in_comment -> continue

  if ch==space ->
      if in_val    -> pass
      elif in_list -> indent+=1
      else         -> throw error

  if ch==':' ->
      if in_list ->
          if current_key[0]=='-' ->
              del current_key[0]
              if current_key[1]==' ' -> current_key[1]
      if current_key in config -> throw error
      in_val=True
      current_key=current
      current=''
      continue

  if ch=={ ->
      if in_val             -> set in_interpolation=True
      elif in_interpolation -> throw error  # nested interpolations
      else                  -> throw error  # interpolation outside value
  if ch==} ->
      if in_interpolation ->
          current+=process(interpolation)
          set in_interpolation=False
          continue
      else -> throw error
  if in_interpolation ->
      interpolation+=ch
      continue

  if ch==\n ->
      if in_interpolation -> throw error    
      if in_val ->
          if current==only spaces -> set in_list=True
          in in_list ->
              list_items.append(del_1st_space_if_relevant(current)) # "...- val"
                                                                    # situation
          config[current_key]=del_1st_space_if_relevant(current)    # "key: val"
                                                                    # situation
      if not in_List -> current_key=''
      current=''
      interpolation=''
      in_val=False
      in_comment=False
      in_interpolation=False

  if in_list ->          
      if not in_val ->
          if not indent_expected ->
              indent_expected = indent
              indent = 0
          elif indent != indent_expected -> throw error

  current+=ch


####### add tests




################################################################################
base
~~~py
current = ''

for ch in text ->
    current+=ch
~~~
################################################################################
ignoring comments

~~~py
current = ''
in_comment = False

for ch in text ->
    # must be present before if in_comment -> continue
    if ch=='\n' -> ...

    if ch=='#'    -> set in_comment=True
    # must be placed after end-line check
    if in_comment -> continue

    current+=ch
~~~
################################################################################
reading values

~~~py

def clean_key(key, in_list:bool) ->
    # strips spaces from beginning ?
    if in_list ->
        rm_1st = key[0] == '-'
        rm_2nd = rm_1st and (key[1]==' ')
        if rm_1st -> remove key[0]
        else      -> throw error
        if rm_2st -> remove key[1]
        return key

def clean_val(val) ->
    if val[0]==' ' -> remove val[0]
    return val

config = {}
current = ''
in_value = False
current_key = ''

for ch in text ->
    if ch==':' ->
        current_key=clean_key(current, in_list)
        current = ''
        set in_value=True
    if ch=='\n' ->
        if in_value ->
            config[current_key]=clean_val(current)

    current+=ch
~~~


################################################################################
spaces
~~~py

config = {}
current = ''
indent=0
indent_expected=0
in_list=False

for ch in text ->

    if ch==' ' ->
       if len(current)==0 ->  # count indent
           indent+=1
           continue

    if ch=='\n' ->
        current = ''
        indent = 0
        continue

    # this has to be put before current+=ch
    # if put after, check would have to be len(current)==1
    # at this moment ch is non-space -> part of key or val
    if len(current)==0 ->  # current ch should be none-space character
        if in_list ->
            if indent_expected==0 ->
                set indent_expected=indent  # first element sets indent
                continue
            if indent==0 ->
                set in_list=False
                set indent_expected=0
                continue
            if indent!=indent_expected -> throw error

        if indent!=0 -> throw error
    #
    #
    current+=ch

~~~
################################################################################
list

~~~py

def only_spaces_in(s) ->
    return s.count(' ')==len(s)

def clean_list_val(v) ->
    len_v = len(v)
    if len_v<2 -> throw error
    if not v[0]=='-' -> throw error
    if s.count(' ') >= len_v -> throw error no empty values allowed
    cut_prefix = 2 if v[0]==' ' else 1
    return v[cut_prefix:]

config={}
current=''
in_value=False
in_list=False

for ch in text ->

    if ch==':' ->
        set in_value=True
        current_key=...
        current=''

    if ch=='\n' ->
        if in_value ->
            if only_spaces_in(current) ->
                set in_list=True
                continue
            elif in_list ->
                config[current_key].append(clean_list_val(current))
                continue
            else ->
                config[current_key]=...
                continue
        # if in_list ->
        #     if current ->
        #         config[current_key].append(clean_list_val(current))


    if len(current)==0 ->  # current ch should be none-space character
        if in_list ->
            if ch=='-' -> set in_value=True

    current+=ch

~~~


################################################################################
interpolations

~~~py

current=''

in_interpolation=False
interpolation=''

for ch in text ->

    if in_value:
        if ch=='{' ->
            set in_interpolation=True
            interpolation+=ch
            continue
        if ch=='}' ->
            current+=process_interpolation(interpolation)
            set in_interpolation=False
            continue

    if ch=='\n':
        if in_interpolation -> throw error
        # if in_value ->
        #     if in_interpolation -> throw error

    current+=ch
~~~

################################################################################
raw

~~~py
current=''
raw_keys=[]
in_raw_value=False

for ch in text ->

    if ch==':' ->
        set in_value=True
        current_key=...
        if current_key in raw_keys ->
            set in_raw_value=True
        current=''

    if in_value:
        if not in_raw_value ->
            if ch=='{' -> ...
            if ch=='}' -> ...

    if ch=='\n':
        if in_raw_value ->
            if not in_list ->
                set in_raw_value=False

    if len(current)==0 ->  # current ch should be none-space character
        if in_list ->
            if indent_expected==0 -> ...
            if indent==0 ->
                set in_list=False
                set in_raw_value=False   
                set indent_expected=0

    current+=ch
~~~

################################################################################

!!! add - as empty value ''
!!! make sure list has at least one element

~~~py
for ch in text ->
    current+=ch
~~~

################################################################################
line and character numbering

~~~py

line_num = 1
char_num = 0

for ch in text ->
    char_num+=1

    if ch=='\n' ->
        line_num+=1
        char_num=0
        continue

    current+=ch
~~~



################################################################################
base
~~~py
for ch in text ->
    current+=ch
~~~




~~~
in_indent=True
current=[]
for ch in text:
    ch==endline:
        if in_key:
            in_list=True
        if in_val:
            if: in_list
            else: # single key-val pair
        # reset relevant
    ch=='#':
    ch==':':
    ch=='-':
    elif in comment:
    elif in_indent:
    elif in_key:
    elif in val:

    current.append(ch)
~~~
~~~
in_indent=True
current=[]
for ch in text
    ch==endline
        if in_key
            in_list=True
        if in_val
            if in_list
            else # single key-val pair
        # reset relevant
    ch=='#':
        in_comment=True
    ch==':':
        in_val=True
    ch=='-':
        in_val=True

    elif in comment:
        continue
    elif in_indent:
        ch==' ':
            # add indent
            continue
        else:  # first non-space character
            in_indent=False

    elif in_key:
    elif in val:

    current.append(ch)
~~~
~~~
in_indent=True
current=[]
for ch in text:

    if in comment: # options comment->endl
        if ch!=endline:
            continue

    elif in_val:  # options val->comment|val->endl
        if ch=='#':
            in_comment=True
            continue
        if ch==endline:
            if in_list:
            else:  # single key-val
            # reset relevant
            continue

    elif in_key:  # options key->val|val->comment|key->endl
        if ch=='#':
            in_comment=True
            continue
        if ch==':':
            in_val=True
            # prepare and save current key
            continue
        if ch==endline:
            # throw error ': expected'

    elif in_indent:  # options ind->key|ind->val|ind->comment|ind->endl
        if ch=='#':
            in_comment=True
            continue
        if ch==endline:  # empty line
            # reset relevant
        if ch==' ':
        elif ch=='-':
            if in_list:
            else:  # throw error keys cannot begin with -
        else:  # first non-space non-hyphen character -> in_key

    current.append(ch)
~~~
~~~
in_indent=True
current=[]
for ch in text:

    if ch==endline:
        if in_val:
        if in_key:
        if in_ind:
        # reset relevant
        continue

    if in comment: continue
    if ch=='#':
        in_comment=True
        continue

    if in_val:
        # do stuff

    elif in_key:
        if ch==':':
            in_val=True
            # prepare and save current key
            continue

    elif in_ind:  # options ind->key|ind->val|ind->comment|ind->endl
        if ch==' ':
        elif ch=='-':
            if in_list:
            else:  # throw error keys cannot begin with -
        else:  # first non-space non-hyphen character -> in_key

#
# why val key ind order ?
# val should be the longest on average(wild guess),
# then keys second longest on average(wild guess),
# then indent (only one level supported)
# most users will probably use 2 or 4 spaces
#
    current.append(ch)
~~~



















https://stackoverflow.com/questions/3533338/python-efficiency-of-and-vs-multiple-ifs



################################################################################
################################################################################
spaces
~~~py
from os import linesep as endline

def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    config = {}
    line_num = 1
    char_num = 0
    in_list = False
    in_indent = True  # each line starts with indent even if it is 0
    current_indent  = 0
    expected_indent = 0

    curert = []
    for ch in text:
        char_num += 1

        if ch == endline:
            line_num += 1
            char_num = 0
            in_indent = True
            current = []
            current_indent = 0
            continue

        if in_indent:
            if ch == ' ':
                current_indent += 1
                continue
            else:  # key|val started -> no more indent
                in_indent = False
                if in_list:
                    if current_indent == 0:  # the list has ended
                        expected_indent = 0
                        in_list = False
                    elif expected_indent == 0:  # single level indent supported
                        expected_indent = current_indent  # 1st element sets indent
                    elif current_indent != expected_indent:
                        raise_err('invalid indentation')
                    # else indent okay
                elif current_indent != 0:  # not in list
                        raise_err('invalid indentation')


        current.append[ch]

    return config
~~~
################################################################################
key/val
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    config = {}
    line_num = 1
    char_num = 0
    in_val = True
    current_key = []
    in_list = False
    in_indent = True  # each line starts with indent even if it is 0
    current_indent  = 0
    expected_indent = 0

    curert = []
    for ch in text:
        char_num += 1

        if ch == ':':
            if ' ' in current: raise_err('space in key')
            if '{' in current: raise_err('interpolation in key')
            if '}' in current: raise_err('interpolation in key')
            current_key = ''.join(current)
            current = []
            in_val = True
            continue

        if ch == endline:
            if in_val:
                if current_key in config: raise_err(f'{current_key} duplicate')
                if current[0] == ' ': current = current[1:]
                config[current_key] = ''.join(current).rstrip()

            line_num += 1
            char_num = 0
            current = []
            current_key = []
            in_val = False
            in_indent = True
            current_indent = 0
            continue

        if in_indent:
            if ch == ' ':
                current_indent += 1
                continue
            else:  # key|val started -> no more indent
                in_indent = False
                if in_list:
                    if current_indent == 0:  # the list has ended
                        expected_indent = 0
                        in_list = False
                    elif expected_indent == 0:  # single level indent supported
                        expected_indent = current_indent  # 1st element sets indent
                    elif current_indent != expected_indent:
                        raise_err('invalid indentation')
                    # else indent okay
                elif current_indent != 0:  # not in list
                        raise_err('invalid indentation')


        current.append[ch]

    return config
~~~
################################################################################
list
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    config = {}
    line_num = 1
    char_num = 0
    in_val = True
    current_key = []
    current_list = []
    in_list = False
    in_indent = True  # each line starts with indent even if it is 0
    current_indent  = 0
    expected_indent = 0

    curert = []
    for ch in text:
        char_num += 1

        if in_indent:
            if ch == ' ':
                current_indent += 1
                continue
            else:  # key|val started -> no more indent
                in_indent = False
                if in_list:
                    if current_indent == 0:  # the list has ended
                        expected_indent = 0
                        in_list = False
                    elif expected_indent == 0:  # single level indent supported
                        expected_indent = current_indent  # 1st element sets indent
                    elif current_indent != expected_indent:
                        raise_err('invalid indentation')
                    # else indent ok
                    if ch != '-':
                        raise_err('list value must begin with hyphon/minus')
                    else:
                        in_val = True
                        continue
                elif current_indent != 0:  # not in list
                        raise_err('invalid indentation')

        if ch == ':':
            if current[0] == '-': raise_err('key cannot begin wth -')
            if ' ' in current: raise_err('key contains space')
            if '{' in current: raise_err('key contains {')
            if '}' in current: raise_err('key contains }')
            current_key = ''.join(current)
            current = []
            in_val = True
            continue

        if ch == endline:
            if in_val:
                if in_list:
                    if current[0] == ' ': current = current[1:]
                    current_list.append(''.join(current).rstrip())
                elif current.count(' ') == len(current):  # key with no val
                    in_list = True
                else:  # in key|val
                    if current_key in config: raise_err(f'{current_key} duplicate')
                    if current[0] == ' ': current = current[1:]
                    config[current_key] = ''.join(current).rstrip()
            line_num += 1
            char_num = 0
            current = []
            current_key = []
            in_val = False
            in_indent = True
            current_indent = 0
            continue

        current.append[ch]

    return config
~~~
################################################################################
comment
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    config = {}
    line_num = 1
    char_num = 0
    in_val = True
    current_key = []
    current_list = []
    in_list = False
    in_comment = False
    in_indent = True  # each line starts with indent even if it is 0
    current_indent  = 0
    expected_indent = 0

    curert = []
    for ch in text:
        char_num += 1

        if ch == endline:
            if in_val:
                if in_list:
                    if current[0] == ' ': current = current[1:]
                    current_list.append(''.join(current).rstrip())
                elif current.count(' ') == len(current):  # key with no val
                    in_list = True
                else:  # in key|val
                    if current_key in config: raise_err(f'{current_key} duplicate')
                    if current[0] == ' ': current = current[1:]
                    config[current_key] = ''.join(current).rstrip()
            line_num += 1
            char_num = 0
            current = []
            current_key = []
            in_val = False
            in_indent = True
            current_indent = 0
            in_comment = False
            continue

        if in_comment: continue
        if ch == '#':
            in_comment = True
            continue

        if in_indent:
            if ch == ' ':
                current_indent += 1
                continue
            else:  # key|val started -> no more indent
                in_indent = False
                if in_list:
                    if current_indent == 0:  # the list has ended
                        expected_indent = 0
                        in_list = False
                    elif expected_indent == 0:  # single level indent supported
                        expected_indent = current_indent  # 1st element sets indent
                    elif current_indent != expected_indent:
                        raise_err('invalid indentation')
                    # else indent ok
                    if ch != '-':
                        raise_err('list value must begin with hyphon/minus')
                    else:
                        in_val = True
                        continue
                elif current_indent != 0:  # not in list
                        raise_err('invalid indentation')

        if ch == ':':
            if current[0] == '-': raise_err('key cannot begin wth -')
            if ' ' in current: raise_err('key contains space')
            if '{' in current: raise_err('key contains {')
            if '}' in current: raise_err('key contains }')
            current_key = ''.join(current)
            current = []
            in_val = True
            continue

        current.append[ch]

    return config
~~~
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
start
~~~py
from os import linesep as endl
from typing import TypeAlias, TextIO


dot_file_config:TypeAlias = ...


class ParsingError(Exception): pass


def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    config = {}

    line_num = 0
    char_num = 0
    in_ind = True  # in_indent, every line begins with indentation (sometimes 0)
    in_val = False
    in_key = False
    in_com = False # in_comment
    current = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                ...
            if in_key:
                ...
            if in_ind:
                ...

            in_ind = True
            in_val = False
            in_key = False
            in_com = False
            current = []
            char_num = 0
            line_num += 1
            continue

        if in_com: continue
        if ch == '#':
            in_com = True
            continue

        if in_val:
            ...
        elif in_key:
            ...
        elif in_ind:
            ...

        current.append[ch]
    return config
~~~
################################################################################
key/val
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    line_num = 0
    char_num = 0
    in_ind = True  # in_indent, every line begins with indentation (even if 0)
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_list = False
    current = []
    current_key = ''
    current_list = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # remove this "key: val" space
                val = ''.join(current).rstrip()

                if in_list:
                    current_list.append(val)
                else:  # single key-val
                    conf[current_key] = val
                    current_key = ''
            if in_key:
                ...
            if in_ind:
                ...

            in_ind = True
            in_val = False
            in_key = False
            in_com = False
            current = []
            char_num = 0
            line_num += 1
            continue

        if in_com: continue
        if ch == '#':
            in_com = True
            continue

        if in_val:
            ...
        elif in_key:
            if ch == ':':
                in_val = True
                errs = []
                if current[0] == '-': errs.append('key cannot begin wth -')
                if ' ' in current: errs.append('key contains space')
                if '{' in current: errs.append('key contains {')
                if '}' in current: errs.append('key contains }')
                key = ''.join(current).rstrip()
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_ind:
            if ch == ':': raise_err('where is the key ?')
            ...

        current.append[ch]
    return conf
~~~
################################################################################
indentaion
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    line_num = 0
    char_num = 0
    exp_ind = 0  # expected indent
    ind_num = 0  # indentation number
    in_ind = True  # in_indent, every line begins with indentation (even if 0)
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_list = False
    current = []
    current_key = ''
    current_list = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # remove this "key: val" space
                val = ''.join(current).rstrip()

                if in_list:
                    current_list.append(val)
                else:  # single key-val
                    conf[current_key] = val
                    current_key = ''
            if in_key:
                ...
            if in_ind:
                ...

            in_ind = True
            in_val = False
            in_key = False
            in_com = False
            current = []
            ind_num = 0
            char_num = 0
            line_num += 1
            continue

        if in_com: continue
        if ch == '#':
            in_com = True
            continue

        if in_val:
            ...
        elif in_key:
            if ch == ':':
                in_val = True
                errs = []
                key = ''.join(current).rstrip()
                if ' ' in key: errs.append('key contains space')
                if '{' in key: errs.append('key contains {')
                if '}' in key: errs.append('key contains }')
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_ind:
            if ch == ' ':
                ind_num += 1
                continue
            elif ch == ':':
                raise_err('key beginning with ":"')
            elif ch == '-':
                if not in_list: raise_err('key beginning with "-"')
                if ind_num == 0: raise_err('un-indented list')

                if exp_ind == 0:
                    exp_ind = ind_num  # first element sets indetntaion
                elif exp_ind != ind_num:
                    raise_err(f'expected {exp_ind} spaces indentaion')
                # else:   # indintation ok
                # in_ind = False  # not needed
                in_val = True
                continue
            else:
                if ind_num != 0: raise_err('unexpected indentaion')
                in_key = True

        current.append[ch]
    return conf
~~~
################################################################################
list
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    line_num = 0
    char_num = 0
    exp_ind = 0  # expected indent
    ind_num = 0  # indentation number
    in_ind = True  # in_indent, every line begins with indentation (even if 0)
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_list = False
    current = []
    current_key = ''
    current_list = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # remove this "key: val" space
                val = ''.join(current).rstrip()  # '   '.rstrip() -> ''

                if in_list:
                    current_list.append(val)
                elif not val:       # 'key:   endl' is always considered a list as
                    in_list = True  # 'key: - endl' is an empty value at this point
                                    # value with only spaces ends as empty string
                    conf[current_key] = current_list
                else: # simple key-val
                    conf[current_key] = val
                    current_key = ''
            if in_key:
                ...
            if in_ind:
                ...

            in_ind = True
            in_val = False
            in_key = False
            in_com = False
            current = []
            ind_num = 0
            char_num = 0
            line_num += 1
            continue

        if in_com: continue
        if ch == '#':
            in_com = True
            continue

        if in_val:
            ...
        elif in_key:
            if ch == ':':
                in_val = True
                errs = []
                key = ''.join(current).rstrip()
                if ' ' in key: errs.append('key contains space')
                if '{' in key: errs.append('key contains {')
                if '}' in key: errs.append('key contains }')
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_ind:
            if ch == ' ':
                ind_num += 1
                continue
            elif ch == ':':
                raise_err('key beginning with ":"')
            elif ch == '-':
                if not in_list: raise_err('key beginning with "-"')
                if ind_num == 0: raise_err('un-indented list')

                if exp_ind == 0:
                    exp_ind = ind_num  # first element sets indetntaion
                elif exp_ind != ind_num:
                    raise_err(f'expected {exp_ind} spaces indentaion')
                # else:   # indintation ok
                # in_ind = False  # not needed
                in_val = True
                continue
            else:
                if inde_num != 0: raise_err('unexpected indentaion')
                in_key = True
                in_list = False
                current_list = []

        current.append[ch]
    return conf
~~~
################################################################################
interpolations
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    im = '\x00'  # interpolated minus, "key: - endl" -> value=='' and
                 # "key: {-} endl" -> value=='-' to differentiate that
                 # unicode null character '\x00' is used temporary
    interpolations = {
        'left_curly'  : '{',              'l'  : '{',
        'right_curly' : '}',              'l'  : '}',
        'space'       : ' ',              's'  : ' ',
        'colon'       : ':',              'c'  : ':',
        'hash'        : '#',              'h'  : '#',
        'hyphen'      : im, 'minus' : im, 'm'  : im,
    }
    line_num = 0
    char_num = 0
    inde_num = 0  # indentation number
    exp_inde = 0  # expected indent
    in_inde = True  # in_indent, every line begins with indentation >= 0
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_list = False
    in_inter = False
    current = []
    current_key = ''
    current_list = []
    current_inter = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # remove this "key: val" space
                val = ''.join(current).rstrip()  # '   '.rstrip() -> ''

                if in_list:
                    current_list.append(val)
                elif not val:       # 'key:   endl' is always considered a list as
                    in_list = True  # 'key: - endl' is an empty value at this point
                                    # value with only spaces ends as empty string
                    conf[current_key] = current_list
                else: # simple key-val
                    conf[current_key] = val
                    current_key = ''
            if in_key:
                ...
            if in_inde:
                ...

            in_inde = True
            in_val = False
            in_key = False
            in_com = False
            in_inter = False
            current = []
            inde_num = 0
            char_num = 0
            line_num += 1
            continue

        if in_com: continue
        if ch == '#':
            in_com = True
            continue

        if in_val:
            if ch == '{':
                in_inter = True
            elif ch == '}':
                inter = interpolations.get(''.join(current_inter), None)
                if inter is None: raise_err(f'unknown interpolation {inter}')
                current.append(inter)
                current_inter = []
                in_inter = False
            elif in_inter:
                if (ch == '{') or (ch == '}'):
                    raise_err('interpolation contains "{" or "}" ')
                current_inter.apped(ch)
        elif in_key:
            if ch == ':':
                in_val = True
                errs = []
                key = ''.join(current).rstrip()
                if ' ' in key: errs.append('key contains space')
                if '{' in key: errs.append('key contains {')
                if '}' in key: errs.append('key contains }')
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_inde:
            if ch == ' ':
                inde_num += 1
                continue
            elif ch == ':':
                raise_err('key beginning with ":"')
            elif ch == '-':
                if not in_list: raise_err('key beginning with "-"')
                if inde_num == 0: raise_err('un-indented list')

                if exp_inde == 0:
                    exp_inde = inde_num  # first element sets indentation
                elif exp_inde != inde_num:
                    raise_err(f'expected {exp_inde} spaces indentaion')
                # else:   # indentation ok
                # in_inde = False  # not needed
                in_val = True
                continue
            else:
                if inde_num != 0: raise_err('unexpected indentaion')
                in_key = True
                in_list = False
                current_list = []

        current.append[ch]
    return conf
~~~
################################################################################
empty value
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    im = '\x00'  # interpolated minus, "key: - endl" -> value=='' and
                 # "key: {-} endl" -> value=='-' to differentiate that
                 # unicode null character '\x00' is used temporary
    interpolations = {
        'left_curly'  : '{',              'l'  : '{',
        'right_curly' : '}',              'l'  : '}',
        'space'       : ' ',              's'  : ' ',
        'colon'       : ':',              'c'  : ':',
        'hash'        : '#',              'h'  : '#',
        'hyphen'      : im, 'minus' : im, 'm'  : im,
    }
    line_num = 0
    char_num = 0
    inde_num = 0  # indentation number
    exp_inde = 0  # expected indent
    in_inde = True  # in_indent, every line begins with indentation >= 0
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_list = False
    in_inter = False
    current = []
    current_key = ''
    current_list = []
    current_inter = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # rm 1st space "key: val"
                val = ''.join(current).rstrip()  # '   '.rstrip() -> ''
                if val:
                    if val == '-': val = ''           # remove empty value marker
                    else: val = val.replace(im, '-')  # {minus/...} -> actual '-'

                    if in_list:
                        current_list.append(val)
                    else:
                        conf[current_key] = val
                        current_key = ''
                else:  # begginning of list or err
                    if in_list: raise_err('empty value in list')
                    in_list = True
                    conf[current_key] = current_list
            if in_key:
                ...
            if in_inde:
                ...

            in_inde = True
            in_val = False
            in_key = False
            in_com = False
            in_inter = False
            current = []
            inde_num = 0
            char_num = 0
            line_num += 1
            continue

        if in_com: continue
        if ch == '#':
            in_com = True
            continue

        if in_val:
            if ch == '{':
                in_inter = True
            elif ch == '}':
                inter = interpolations.get(''.join(current_inter), None)
                if inter is None: raise_err(f'unknown interpolation {inter}')
                current.append(inter)
                current_inter = []
                in_inter = False
            elif in_inter:
                if (ch == '{') or (ch == '}'):
                    raise_err('interpolation contains "{" or "}" ')
                current_inter.apped(ch)
        elif in_key:
            if ch == ':':
                in_val = True
                errs = []
                key = ''.join(current).rstrip()
                if ' ' in key: errs.append('key contains space')
                if '{' in key: errs.append('key contains {')
                if '}' in key: errs.append('key contains }')
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_inde:
            if ch == ' ':
                inde_num += 1
                continue
            elif ch == ':':
                raise_err('key beginning with ":"')
            elif ch == '-':
                if not in_list: raise_err('key beginning with "-"')
                if inde_num == 0: raise_err('un-indented list')

                if exp_inde == 0:
                    exp_inde = inde_num  # first element sets indentation
                elif exp_inde != inde_num:
                    raise_err(f'expected {exp_inde} spaces indentaion')
                # else:   # indentation ok
                # in_inde = False  # not needed
                in_val = True
                continue
            else:
                if inde_num != 0: raise_err('unexpected indentaion')
                in_key = True
                in_list = False
                current_list = []

        current.append[ch]
    return conf
~~~
################################################################################
raw mode
~~~py
raw_keys = []

def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    im = '\x00'  # interpolated minus, "key: - endl" -> value=='' and
                 # "key: {-} endl" -> value=='-' to differentiate that
                 # unicode null character '\x00' is used temporary
    interpolations = {
        'left_curly'  : '{',              'l'  : '{',
        'right_curly' : '}',              'l'  : '}',
        'space'       : ' ',              's'  : ' ',
        'colon'       : ':',              'c'  : ':',
        'hash'        : '#',              'h'  : '#',
        'hyphen'      : im, 'minus' : im, 'm'  : im,
    }
    line_num = 0
    char_num = 0
    inde_num = 0  # indentation number
    exp_inde = 0  # expected indent
    in_inde = True  # in_indent, every line begins with indentation >= 0
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_raw = False  # in_raw_val
    in_list = False
    in_inter = False
    current = []
    current_key = ''
    current_list = []
    current_inter = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # rm 1st space "key: val"
                val = ''.join(current).rstrip()  # '   '.rstrip() -> ''
                # maby strip ????????? ^ here
                if val:
                    if val == '-': val = ''           # remove empty value marker
                    else: val = val.replace(im, '-')  # {minus/...} -> actual '-'

                    if in_list:
                        current_list.append(val)
                    else:
                        conf[current_key] = val
                        current_key = ''
                else:  # begginning of list or err
                    if in_list: raise_err('empty value in list')
                    in_list = True
                    conf[current_key] = current_list
            if in_key:
                ...
            if in_inde:
                ...

            if not in_list: in_raw = False

            in_inde  = True ;    line_num += 1
            in_val   = False;    char_num  = 0
            in_key   = False;    inde_num  = 0
            in_com   = False;    current   = []
            in_inter = False
            continue

        if in_raw:
            pass
        else:
            if in_com: continue
            if ch == '#':
                in_com = True
                continue

        if in_val:
            if in_raw:
                pass
            elif ch == '{':
                in_inter = True
            elif ch == '}':
                inter = interpolations.get(''.join(current_inter), None)
                if inter is None: raise_err(f'unknown interpolation {inter}')
                current.append(inter)
                current_inter = []
                in_inter = False
            elif in_inter:
                if (ch == '{') or (ch == '}'):
                    raise_err('interpolation contains "{" or "}" ')
                current_inter.apped(ch)
        elif in_key:
            if ch == ':':
                in_val = True
                key = ''.join(current).rstrip()
                if key.startswith("r'"):
                    in_raw = True
                    key = key[2:]
                elif key in raw_keys:
                    in_raw = True
                errs = []
                if ' ' in key: errs.append('key contains space')
                if '{' in key: errs.append('key contains {')
                if '}' in key: errs.append('key contains }')
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_inde:
            if ch == ' ':
                inde_num += 1
                continue
            elif ch == ':':
                raise_err('key beginning with ":"')
            elif ch == '-':
                if not in_list: raise_err('key beginning with "-"')
                if inde_num == 0: raise_err('un-indented list')

                if exp_inde == 0:
                    exp_inde = inde_num  # first element sets indentation
                elif exp_inde != inde_num:
                    raise_err(f'expected {exp_inde} spaces indentaion')
                # else:   # indentation ok
                # in_inde = False  # not needed ?
                in_val = True
                continue
            else:
                if inde_num != 0: raise_err('unexpected indentaion')
                if in_list: raise_err('"-" expected before value')
                in_list = False
                in_raw = False
                in_key = True
                current_list = []

        current.append[ch]
    return conf
~~~
################################################################################
standalone keys
~~~py
def _parse(text:str, raw_names:list[str]) -> dot_file_config:
    def raise_err(msg:str) -> None:
        raise ParsingError(f'line {line_num}:{char_num} {msg}')

    conf = {}

    im = '\x00'  # interpolated minus, "key: - endl" -> value=='' and
                 # "key: {-} endl" -> value=='-' to differentiate that
                 # unicode null character '\x00' is used temporary
    interpolations = {
        'left_curly'  : '{',              'l'  : '{',
        'right_curly' : '}',              'l'  : '}',
        'space'       : ' ',              's'  : ' ',
        'colon'       : ':',              'c'  : ':',
        'hash'        : '#',              'h'  : '#',
        'hyphen'      : im, 'minus' : im, 'm'  : im,
    }
    line_num = 0
    char_num = 0
    inde_num = 0  # indentation number
    exp_inde = 0  # expected indent
    in_inde = True  # in_indent, every line begins with indentation >= 0
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_raw = False  # in_raw_val
    in_list = False
    in_inter = False
    current = []
    current_key = ''
    current_list = []
    current_inter = []
    for ch in text:
        char_num += 1

        if ch == endl:
            if in_val:
                if current[0] == ' ': current = current[1:]  # rm 1st space "key: val"
                val = ''.join(current).rstrip()  # '   '.rstrip() -> ''
                # maby strip ????????? ^ here
                if val:
                    if val == '-': val = ''           # remove empty value marker
                    else: val = val.replace(im, '-')  # {minus/...} -> actual '-'

                    if in_list:
                        current_list.append(val)
                    else:
                        conf[current_key] = val
                        current_key = ''
                else:  # begginning of list or err
                    if in_list: raise_err('empty value in list')
                    in_list = True
                    conf[current_key] = current_list
            if in_key:  # standalone-key or error
                # if in_list: raise_err('standalone keys in list, unsupported') # no need
                key = ''.join(current).rstrip()
                val = standalone_keys.get(key, object)
                if val is object: raise_err(f'invalid standalone key {key}')
            # if in_inde: pass  # empty line

            if not in_list: in_raw = False

            in_inde  = True ;    line_num += 1
            in_val   = False;    char_num  = 0
            in_key   = False;    inde_num  = 0
            in_com   = False;    current   = []
            in_inter = False
            continue

        if in_raw: pass
        else:
            if in_com: continue
            if ch == '#':
                in_com = True
                continue

        if in_val:
            if in_raw:
                pass
            elif ch == '{':
                in_inter = True
            elif ch == '}':
                inter = interpolations.get(''.join(current_inter), None)
                if inter is None: raise_err(f'unknown interpolation {inter}')
                current.append(inter)
                current_inter = []
                in_inter = False
            elif in_inter:
                if (ch == '{') or (ch == '}'):
                    raise_err('interpolation contains "{" or "}" ')
                current_inter.apped(ch)
        elif in_key:
            # if in_list: raise_err('list supports only values')
            if ch == ':':
                in_val = True
                key = ''.join(current).rstrip()
                # if key.startswith("r'"):
                #     in_raw = True
                #     key = key[2:]
                elif key in raw_keys:
                    in_raw = True
                errs = []
                if ' ' in key: errs.append('key contains space')
                if '{' in key: errs.append('key contains {')
                if '}' in key: errs.append('key contains }')
                if key in conf: errs.append(f'"{key}" already exists')
                if errs: raise_err(''.join(errs))
                current_key = key
        elif in_inde:
            if ch == ' ':
                inde_num += 1
                continue
            elif ch == ':':
                raise_err('key beginning with ":"')
            elif ch == '-':
                if not in_list: raise_err('key beginning with "-"')
                if inde_num == 0: raise_err('un-indented list')

                if exp_inde == 0:
                    exp_inde = inde_num  # first element sets indentation
                elif exp_inde != inde_num:
                    raise_err(f'expected {exp_inde} spaces indentaion')
                # else:   # indentation ok
                # in_inde = False  # not needed ?
                in_val = True
                continue
            else:
                if inde_num != 0: raise_err('unexpected indentaion')
                if in_list:
                    raise_err('"-" expected before value')
                in_list = False
                in_raw = False
                in_key = True
                current_list = []

        current.append[ch]
    return conf
~~~
################################################################################
################################################################################
after test rewrite
################################################################################
################################################################################
~~~py
from pathlib import Path
from itertools import chain
from logging import getLogger
from os import linesep as endl

from typing import TypeAlias, TextIO, Annotated, Sequence

dot_file_config:TypeAlias = dict[str, str|list[str]]


class ParsingError(Exception): pass


log = getLogger()
# standalone_keys = {}
forbiden_key_characters = {
    ' ': 'space, ',
    '\t': 'tab, ',
    '{': '"{", ',
    '}': '"}", ',
}


def parse_s(
    text:str|TextIO,
    standalone:dict[str, Annotated[Sequence, 2]]|None=None  # ponder if alloaw only for Sequence[str]
) -> dot_file_config:
    '''
    # basic idea is

    in_indent = True             # each line starts with indentation >= 0
    current = []
    for ch in text:              # read single chartacte at a time
        ch == endl:              # handle what happens wthen endline is reached
            if in_val:           #     while processing val
                ...
            if in_key:           #     while processing key
                ...
            # if in_ind:         #     while processing indent, line must be
            #     ...            #     empty +- comments, section can be skipped
            reset relevant values
            continue

        if in comment: continue  # skip chars until endl when processing comment
                                 # endline charactrer check must be done first
        ch=='#':
            in_comment = True
            continue

        if in_val:               # process value
            ...
        elif in_key:             # process key
            ...
        elif in_indent:          # process indent
            ...

        current.append(ch)
    '''
    def raise_err(msg:str, l_num:int|None=None) -> None:
        where = f'{line_num}:{char_num}' if l_num is None else l_num
        raise ParsingError(f'line {where} {msg}')

    standalone = {} if standalone is None else standalone
    conf:dot_file_config = {}

    inter_m = '\x11'  # interpolated minus, "key: - endl" -> val=='' and
                      # "key: {-} endl" -> value=='-' to differentiate that
                      # unicode dc1 character is used temporary
    inter_s = '\x12'  # interpolating spaces, "key:    endl" -> val=='' -> list
                      # and "key: {s} endl" -> val==' ' to to avoid val.rstrip
                      # unicode dc2 character is used temporary
    interpolations = {
        'left_curly'  : '{',                        'l'  : '{',
        'right_curly' : '}',                        'r'  : '}',
        'colon'       : ':',                        'c'  : ':',
        'hash'        : '#',                        'h'  : '#',
        'space'       : inter_s,                    's'  : inter_s,
        'hyphen'      : inter_m, 'minus' : inter_m, 'm'  : inter_m,
    }
    line_num = 1
    char_num = 0
    inde_num = 0  # indentation number
    exp_inde = 0  # expected indent
    in_inde = True  # in_indent, every line begins with indentation >= 0
    in_val = False
    in_key = False
    in_com = False  # in_comment
    in_raw = False  # in_raw_val
    in_list = False
    in_inter = False
    current:list[str] = []
    current_key = ''
    current_list = []
    current_inter:list[str] = []
    for ch in chain(text, '\n'):
        char_num += 1

        if ch == '\n':
            if in_val:
                if current and current[0] == ' ': current = current[1:]  # rm 1st space "key: val"
                val = ''.join(current).rstrip()  # '   '.rstrip() -> ''

                if val:
                    if in_list:
                        val = val.replace(inter_m, '-').replace(inter_s, ' ')
                        current_list.append(val)
                    else:
                        if val == '-': val = ''  # remove empty value marker
                        val = val.replace(inter_m, '-').replace(inter_s, ' ')
                        conf[current_key] = val
                        current_key = ''
                else:  # begginning of list or err
                    if in_list: raise_err('empty value in list')
                    in_list = True
                    conf[current_key] = current_list
            elif in_key:  # standalone-key or error
                key = ''.join(current).rstrip()
                val:str|object = standalone.pop(key, object)  # type: ignore[no-redef]
                if val is object: raise_err(f'unknown standalone key "{key}"')
                conf[key] = val[0]
            # if in_inde: pass  # empty line

            if not in_list: in_raw = False
            in_inde  = True ;    line_num += 1
            in_val   = False;    char_num  = 0
            in_key   = False;    inde_num  = 0
            in_com   = False;    current   = []
            in_inter = False
            continue

        if in_raw: pass
        elif in_com: continue
        elif ch == '#': in_com = True; continue

        if in_val:
            if in_raw:
                pass
            elif ch == '{':
                if in_inter: raise_err('interpolation contains "{"')
                in_inter = True
                continue
            elif ch == '}':
                if not in_inter: raise_err('not opened interpolation')
                inter_cont = ''.join(current_inter)
                inter = interpolations.get(inter_cont, None)
                if inter is None: raise_err(f'unknown interpolation "{inter_cont}"')
                current.append(inter)  # type: ignore[arg-type] # inter cannot be None
                current_inter = []
                in_inter = False
                continue
            elif in_inter:
                current_inter.append(ch)
                continue
        elif in_key:
            if ch == ':':
                in_val = True
                key = ''.join(current).rstrip()
                if key.startswith("r'"):
                    in_raw = True
                    key = key[2:]
                errs = [forbiden_key_characters[c] for c in key
                        if c in forbiden_key_characters]
                if errs: raise_err('key contains ' + ''.join(errs))
                if key in conf: raise_err(f'duplicated key "{key}"')
                current_key = key
                current = []
                continue
        elif in_inde:
            if ch == ' ':
                inde_num += 1
                continue
            elif ch == '\t':
                raise_err('tab in indentation')
            elif ch == ':':
                raise_err('empty key')
            elif ch == '-':
                if not in_list: raise_err(
                    'key begins with "-", tip: in r\'keys: # comment is value')
                if inde_num == 0 and not exp_inde: raise_err('unindented list')

                if exp_inde == 0:
                    exp_inde = inde_num  # first element sets indentation
                elif exp_inde != inde_num:
                    raise_err(f'expected {exp_inde} spaces indentation')
                # else: pass  # indentation ok
                in_val = True
                continue
            else:
                if inde_num != 0:
                    raise_err('unexpected indentation')
                elif in_list and not current_list:
                    raise_err(f'use "{current_key}: -" for empty value', line_num-1)

                current_list = []
                exp_inde = 0
                in_list = False
                if in_raw and ch == '#':  # comment exits raw mode list
                    in_com = True;        # r'this:
                    in_raw = False        #     - exact
                    continue              # # situation
                in_key = True
                in_raw = False

        current.append(ch)

    if in_list and conf[current_key] == []:  # last "line": 'key: ' -> {current_key: []}
        raise_err(f'use "{current_key}: -" for empty value' , -1)

    for k, v, in standalone.items():
        on_present, on_absent = v
        conf[k] = on_absent
    return conf


def parse_f(
    file:str|Path,
    standalone:dict[str, Annotated[Sequence, 2]]|None=None
) -> dot_file_config:
    with open(file, 'r', encoding='utf-8') as f:
        log.info(f'opening {file}')
        try:
            return parse_s(f.read(), standalone=standalone)
        except (OSError, ParsingError) as e:
            log.error(f'{file} {e.__class__.__name__}: {e}')
            raise e

~~~


fun:
https://stackoverflow.com/questions/2069662/how-to-exit-an-if-clause#comment1999361_2069670

https://stackoverflow.com/questions/2988211/how-can-i-read-a-single-character-at-a-time-from-a-file-in-python

#
